package com.yanzhengwu.boot.core.datasource;

import com.yanzhengwu.boot.core.druid.connect.ConnectWarp;
import com.yanzhengwu.boot.core.transaction.TransactionCarrier;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Optional;

/**
 * 获取数据源（依赖于 spring）  定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法，该方法可以实现数据库的动态切换
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    //所有数据源
    private Map<Object, Object> dataSources;


    //把当前事物下的连接塞入,用于事物处理
    ThreadLocal<TransactionCarrier> transactionCarrierThreadLocal = new ThreadLocal<>();

    public static DynamicDataSource build() {
        return new DynamicDataSource();
    }

    /**
     * 获取与数据源相关的key
     * 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
     * 在通过determineTargetDataSource获取目标数据源时使用
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceType = DynamicDataSourceContextHolder.getDataSourceType();

        System.out.println("获取到数据源： " + dataSourceType);
        return dataSourceType;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.dataSources = targetDataSources;
        super.setTargetDataSources(this.dataSources);
    }

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        dataSources.forEach((k, v) -> logger.info("成功注册数据源:  " + k));
    }

    /**
     * TODO 自己管理事务和连接 此方法是关键，要缓存连接 事务结束时进行提交或回滚
     *
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {
        Optional<TransactionCarrier> currentTransactionCarrier = getCurrentTransactionCarrier();
        if (currentTransactionCarrier.isPresent()) {
            TransactionCarrier transactionCarrier = currentTransactionCarrier.get();
            //开了事物 那么从 currentTransactionCarrier中去获取对应的 connect;
            String currentName = (String) determineCurrentLookupKey();

            Optional<Connection> transactionConnect = transactionCarrier.getConnect(currentName);
            //使用了已经开启了事务的connect;
            if (transactionConnect.isPresent()) {
                return transactionConnect.get();
            }
            //开启事物后第一次获取connect, 那么先获取一个新的 connect
            Connection connection = new ConnectWarp(determineTargetDataSource().getConnection());
            //把新获取到的 connection 放入 transactionCarrier中,后续再次获取就能直接拿到
            transactionCarrier.addTransactionConnect(currentName, connection);
            return connection;
        } else {
            //没开事物 直接走
            return determineTargetDataSource().getConnection();
        }

    }

    /**
     * 当前线程开启了事务
     *
     * @param transactionType
     */
    public void beginTransaction(int transactionType) {
        Optional<TransactionCarrier> currentTransactionCarrier = getCurrentTransactionCarrier();
        //已经开启过了,不在重复开启了
        if (currentTransactionCarrier.isPresent()) {
            return;
        }
        TransactionCarrier transactionCarrier = new TransactionCarrier(transactionType);
        transactionCarrierThreadLocal.set(transactionCarrier);
    }

    private Optional<TransactionCarrier> getCurrentTransactionCarrier() {
        return Optional.ofNullable(transactionCarrierThreadLocal.get());
    }


    /**
     * 提交事物
     *
     * @throws SQLException
     */
    public void commitTransaction() throws SQLException {
        TransactionCarrier transactionCarrier = getCurrentTransactionCarrier().orElseThrow(() -> new RuntimeException("当前线程中事物没有开启或已提交 请勿重复提交！"));
        transactionCarrier.commitTransaction();

        //提交事物后清理释放资源
        clearTransaction();
    }

    private void clearTransaction() {
        transactionCarrierThreadLocal.remove();
    }

    /**
     * 撤销事物
     *
     * @throws SQLException
     */
    public void rollbackTransaction() throws SQLException {
        getCurrentTransactionCarrier().orElseThrow(() -> new RuntimeException("当前线程中事物没有开启或已关闭"))
                .rollbackTransaction();
        //提交事物后清理释放资源
        clearTransaction();
    }



}
